package com.raulraja.model.persistence.impl;

import com.raulraja.model.domain.PaginatedList;
import com.raulraja.model.persistence.PersistenceAdapter;
import org.apache.log4j.Logger;
import org.hibernate.*;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.hibernate.metadata.ClassMetadata;
import org.springframework.orm.hibernate3.HibernateAccessor;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import java.io.Serializable;
import java.sql.SQLException;
import java.util.List;

/**
 * Hibernate spring based impl of the persistence adapter
 */
public class HibernatePersistenceAdapterImpl<Entity> extends HibernateDaoSupport implements PersistenceAdapter<Entity> {

	private final static Logger log = Logger.getLogger(HibernatePersistenceAdapterImpl.class);

	private Integer defaultResultSize = 10;

	public void setDefaultResultSize(Integer defaultResultSize) {
		this.defaultResultSize = defaultResultSize;
	}

	/**
	 * Returns a paginated list of all entities for this class
	 *
	 * @param entityClass the entity class
	 * @param start	   the start record
	 * @param limit	   the limit
	 * @return the list of matches
	 */
	@SuppressWarnings("unchecked")
	public PaginatedList<Entity> list(Class<Entity> entityClass, int start, int limit) {
		limit = limit > 0 ? limit : defaultResultSize;
		if (entityClass == null)
            return null;

        List<Entity> entities;
		PaginatedList<Entity> page = new PaginatedList<Entity>();

		int total;
		try {
			DetachedCriteria criteria = DetachedCriteria.forClass(entityClass);
			entities = (List<Entity>) getPreparedHibernateTemplate().findByCriteria(criteria, start, limit);
			total = count(entityClass);
			page = new PaginatedList<Entity>(total, start, entities);
        }
        catch (Throwable e) {
            log.error(e, e);
        }
        return page;
	}

	/**
	 * Returns a paginated list of the selected property for this class
	 *
	 * @param entityClass the entity class
	 * @param start	   the start record
	 * @param limit	   the limit
	 * @return the list of matches
	 */
	public PaginatedList<?> list(Class<Entity> entityClass, String property, int start, int limit) {
		limit = limit > 0 ? limit : defaultResultSize;
		if (entityClass == null)
            return null;

        List<Object> properties;
		PaginatedList<Object> page = new PaginatedList<Object>();

		int total;
		try {
			DetachedCriteria criteria = DetachedCriteria.forClass(entityClass);
			criteria.setProjection(Projections.property(property));
			properties = (List<Object>) getPreparedHibernateTemplate().findByCriteria(criteria, start, limit);
			total = count(entityClass);
			page = new PaginatedList<Object>(total, start, properties);
        }
        catch (Throwable e) {
            log.error(e, e);
        }
        return page;
	}

	private Integer count(final Class<Entity> entityClass) {
		return (Integer) getPreparedHibernateTemplate().execute(new HibernateCallback(){
			public Object doInHibernate(Session session) throws HibernateException, SQLException {
				Criteria criteria = session.createCriteria(entityClass);
				criteria.setProjection(Projections.rowCount());
				return criteria.list().get(0);
			}
		});
	}

	/**
	 * Find a persistent object based on its unique id
	 *
	 * @param entityClass the entity's class
	 * @param id		  unique identifier
	 * @return returns a persistent object
	 */
	@SuppressWarnings("unchecked")
	public Entity get(Class<Entity> entityClass, Serializable id) {
		Entity entity = (Entity) getHibernateTemplate().get(entityClass, id);
		return initializeProxy(entity);
	}

	/**
	 * Persists an object
	 *
	 * @param entity to be persisted
	 */
	@SuppressWarnings("unchecked")
	public Entity save(Entity entity) {
		getHibernateTemplate().saveOrUpdate(entity);
		return initializeProxy(entity);
	}

	/**
	 * Removes an entity from the data store
	 *
	 * @param entity to be removed
	 */
	@SuppressWarnings("unchecked")
	public Entity delete(Entity entity) {
		getHibernateTemplate().delete(entity);
		return initializeProxy(entity);
	}


	private HibernateTemplate getPreparedHibernateTemplate() {
        HibernateTemplate template = getHibernateTemplate();
        template.setCacheQueries(true);
        template.setFlushMode(HibernateAccessor.FLUSH_AUTO);
        return template;
    }

	private Entity initializeProxy(Entity entity) {
		getPreparedHibernateTemplate().initialize(entity);
		return entity;
	}

	/**
	 * Finds a class associated with this path name
	 *
	 * @param name the name
	 * @return the found class if any
	 */
	@SuppressWarnings("unchecked")
	public Class<Entity> getClassForName(String name) {
		final String normalizedName = name.length() > 0 ? name.substring(name.lastIndexOf("."),1).toUpperCase() + name.substring(0) : name;
		return (Class<Entity>) getPreparedHibernateTemplate().execute(new HibernateCallback(){
			public Object doInHibernate(Session session) throws HibernateException, SQLException {
				SessionFactory factory = session.getSessionFactory();
				ClassMetadata classMetadata = factory.getClassMetadata(normalizedName);
				return classMetadata != null ? classMetadata.getMappedClass(EntityMode.POJO) : null;
			}
		});
	}
}

